﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Roslyn.Services;
using System.IO;
using Roslyn.Compilers;

namespace RoslynProcess
{
    public abstract class RoslynProcess
    {
        private IWorkspace currentWorkspace;
        private ISolution currentSolution;
        private IProject currentProject;
        protected IDocument CurrentDocument { get; private set; }

        private readonly TextWriter outStream;
        protected readonly TextReader inStream;

        public RoslynProcess(TextWriter outStream, TextReader inStream)
        {
            this.outStream = outStream;
            this.inStream = inStream;
        }
        protected RoslynProcess() : this(System.Console.Out, System.Console.In) { }

        void LoadSolution(string solutionPath)
        {
            // TODO Don't reload?
            // Workaround for known bug:
            // http://connect.microsoft.com/VisualStudio/feedback/details/698292/workspace-loadsolution-loads-projects-references-for-mscorlib-and-system-core-only
            currentWorkspace = Workspace.LoadSolution(solutionPath);
            if (currentWorkspace.CurrentSolution.Projects
                .All(p => p.MetadataReferences.Count() <= 2))
            {
                currentWorkspace = Workspace.LoadSolution(solutionPath, platform: "x86");
            }
            currentSolution = currentWorkspace.CurrentSolution;
        }

        public void MainLoop()
        {
            while (true)
            {
                string line = inStream.ReadLine();
                if (line == null)
                {
                    return;
                }
                int spaceLoc = line.IndexOf(' ');
                string cmd, args;
                if (spaceLoc != -1)
                {
                    cmd = line.Substring(0, spaceLoc);
                    args = line.Substring(spaceLoc + 1);
                }
                else
                {
                    cmd = line;
                    args = null;
                }
                HandleCommand(cmd, args);
            }
        }

        protected virtual bool HandleCommand(string cmd, string args)
        {
            if (cmd.Equals("LoadSolution"))
            {
                LoadSolution(args);
            }
            else if (cmd.Equals("ChooseProject"))
            {
                // TODO Error handling.
                currentProject = currentSolution
                    .GetProjectsByName(args)
                    .First();
            }
            else if (cmd.Equals("ChooseDocument"))
            {
                // TODO Error handling.
                CurrentDocument = currentProject.Documents
                    .FirstOrDefault(doc => doc.Name.Equals(args));
            }
            else if (cmd.Equals("LoadDocument"))
            {
                // LoadDocument must always immediately follow ChooseDocument
                //  is there is any chance the document is not loaded.
                if (CurrentDocument == null)
                {
                    DocumentId docId;
                    currentSolution = currentSolution.AddDocument(currentProject.Id, args, out docId);
                    currentProject = currentSolution.GetProject(currentProject.Id);
                    CurrentDocument = currentProject.GetDocument(docId);
                }
            }
            else if (cmd.Equals("ReloadDocument"))
            {
                // TODO Not sure how to do this...
                throw new NotImplementedException();
            }
            else if (cmd.Equals("EditDocument"))
            {
                currentSolution = currentSolution.UpdateDocument(
                    //Task editDoc = currentWorkspace.UpdateDocumentAsync(
                    CurrentDocument.Id, LinesReader.ReadLinesAsIText(inStream, int.Parse(args)));
                //editDoc.Wait();
                // TODO Are these two lines necessary?
                currentProject = currentSolution.GetProject(currentProject.Id);
                CurrentDocument = currentProject.GetDocument(CurrentDocument.Id);
            }
            else if (cmd.Equals("DoneLoading"))
            {
                // Called after all EditDocument calls are complete to allow for
                //  pre-computation.
                // Not sure if this actually makes sense...
            }
            else
            {
                return false;
            }
            return true;
        }

        protected void InsertText(string text, int offset)
        {
            string currentText = CurrentDocument.GetText().GetText();
            string newText = currentText.Insert(offset, text);
            IText newIText = new StringText(newText);
            currentSolution = currentSolution.UpdateDocument(
                CurrentDocument.Id, newIText);
            currentProject = currentSolution.GetProject(currentProject.Id);
            CurrentDocument = currentProject.GetDocument(CurrentDocument.Id);
        }

        protected void WriteLine(string line)
        {
            lock (outStream)
            {
                outStream.WriteLine(line);
            }
        }
    }
}
